﻿#pragma execution_character_set("utf-8")
#include "MyTcpServerWidget.h"
#include <qdatetime.h>
#include <qdesktopwidget.h>
#include <qfiledialog.h>
#include <qtextstream.h>
#include <qdebug.h>
#include <QUuid>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTextStream>
#include <xlnt/xlnt.hpp>
// #include <QXmlStreamWriter>

extern "C"
{
#include "aes_yd.h"
}

static const uint16_t crc16_table[256] = {
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
	0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231,
	0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318,
	0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443,
	0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,
	0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630,
	0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF,
	0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861,
	0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A,
	0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33,
	0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97,
	0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F,
	0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188,
	0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080,
	0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398,
	0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290,
	0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,
	0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481,
	0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F,
	0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676,
	0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A,
	0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
	0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
	0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26,
	0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F,
	0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17,
	0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};

static uint16_t mj_crc16(const void *buf, int len)
{
	uint16_t crc = 0;
	const uint8_t *p = (const uint8_t *)buf;
	while (len--)
		crc = (crc << 8) ^ crc16_table[((crc >> 8) ^ *p++) & 0xff];
	return crc;
}

int MyTcpServerWidget::encode_payload(const QString imei, const QByteArray &data, QByteArray &payload)
{

	QDataStream stream(&payload, QIODevice::WriteOnly);
	// aa ff
	stream << (quint8)0xbb << (quint8)0xff;
	// reserve total length
	stream << (quint16)0;

	// imei
	QByteArray imei_bcd = QByteArray::fromHex('0' + imei.toLatin1());
	stream.writeRawData(imei_bcd, 8);

	// data length
	stream << (quint16)data.size();

	// data
	QByteArray data_enc = data;
	// add separator 0xda
	data_enc.append((char)0xda);
	int mod = (data_enc.size() + 2) % 16;
	if (mod != 0)
	{
		int pad = 16 - mod;
		data_enc.append(QByteArray(pad, 0));
	}

	quint16 crc_0 = yd_crc16(data_enc.constData(), data_enc.size());
	data_enc.append((char)(crc_0 >> 8));
	data_enc.append((char)(crc_0 & 0xff));

	// encrypt
	yd_enc((uint8_t *)data_enc.data(), data_enc.size(), (uint8_t *)data_enc.data());

	stream.writeRawData(data_enc, data_enc.size());

	// write total length, crc(2), 0x0d 0x0a
	quint16 total_length = payload.size() + 2 + 2;
	stream.device()->seek(2);
	stream << total_length;

	// write crc
	quint16 crc = mj_crc16(payload.constData(), payload.size());
	stream.device()->seek(payload.size());
	stream << crc;

	// end
	stream << (quint8)0x0d << (quint8)0x0a;

	return 0;
}

QString MyTcpServerWidget::decode_payload(const QByteArray &data)
{
	QDataStream stream(data);
	quint16 cmd;
	stream >> cmd;

	QJsonObject json;
	qint8 year, month, day, hour, minute, second;

	switch (cmd)
	{
	case 0x0001: // login
	{
		//// desc
		json["desc"] = "login";
		//// iccid bcd
		QByteArray iccid_bcd(10, 0);
		stream.readRawData(iccid_bcd.data(), 10);
		json["iccid"] = QString::fromLatin1(iccid_bcd.toHex());

		// // to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0xf001: // login response
	{
		// desc
		json["desc"] = "login response";
		// result
		quint8 result;
		stream >> result;
		json["result"] = result;
		// time
		quint8 year, month, day, hour, minute, second;
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x0002: // gps
	{
		// desc
		json["desc"] = "GPS";
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));

		// number of statellite
		quint8 number_of_statellite;
		stream >> number_of_statellite;
		json["statellite"] = number_of_statellite;
		// longitude
		quint32 longitude;
		stream >> longitude;
		// high byte is east or west
		if (longitude & 0x80000000)
		{
			json["longitude"] = QString::number((double)(longitude & 0x7fffffff) / 1000000, 'f', 6) + "W";
		}
		else
		{
			json["longitude"] = QString::number((double)longitude / 1000000, 'f', 6) + "E";
		}
		// latitude
		quint32 latitude;
		stream >> latitude;
		// high byte is north or south
		if (latitude & 0x80000000)
		{
			json["latitude"] = QString::number((double)(latitude & 0x7fffffff) / 1000000, 'f', 6) + "S";
		}
		else
		{
			json["latitude"] = QString::number((double)latitude / 1000000, 'f', 6) + "N";
		}
		// speed
		quint16 speed;
		stream >> speed;
		json["speed"] = QString::number((double)speed / 100.0, 'f', 1) + "m/s";
		// convert to km/h
		json["speed_km"] = QString::number((double)speed * 3.6 / 100.0, 'f', 1) + "km/h";

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x0003: // alarm
	{
		// desc
		json["desc"] = "alarm";
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));

		// type
		quint8 type;
		stream >> type;
		json["type"] = type;
		if (type == 0x00)
		{
			json["type_desc"] = "shock";
		}
		else if (type == 0x01)
		{
			json["type_desc"] = "main power lost";
		}
		else if (type == 0x02)
		{
			json["type_desc"] = "battery communication error";
		}

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x0004: // Battery
	{
		// desc
		json["desc"] = "Battery";
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));
		// current
		quint16 current;
		stream >> current;
		json["current"] = QString::number((current - 30000) / 10.0, 'f', 1) + "A";
		// sum voltage
		quint16 sum_voltage;
		stream >> sum_voltage;
		json["sum_voltage"] = QString::number(sum_voltage) + "mV";
		// soc
		quint16 soc;
		stream >> soc;
		json["soc"] = QString::number(soc / 10.0, 'f', 1) + "%";
		// soh
		quint16 soh;
		stream >> soh;
		json["soh"] = QString::number(soh / 10.0, 'f', 1) + "%";
		// max cell voltage
		quint16 max_cell_voltage;
		stream >> max_cell_voltage;
		json["max_cell_voltage"] = QString::number(max_cell_voltage) + "mV";
		// min cell voltage
		quint16 min_cell_voltage;
		stream >> min_cell_voltage;
		json["min_cell_voltage"] = QString::number(min_cell_voltage) + "mV";
		// max cell temperature
		quint16 max_cell_temperature;
		stream >> max_cell_temperature;
		json["max_cell_temperature"] = QString::number(((int)max_cell_temperature - 400) / 10.0, 'f', 1) + "°C";
		// min cell temperature
		quint16 min_cell_temperature;
		stream >> min_cell_temperature;
		json["min_cell_temperature"] = QString::number(((int)min_cell_temperature - 400) / 10.0, 'f', 1) + "°C";
		// cycle
		quint16 cycle;
		stream >> cycle;
		json["cycle"] = QString::number(cycle);

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x0005: // remote control response
	{
		// desc
		json["desc"] = "remote control response";
		quint8 year, month, day, hour, minute, second;
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));
		// result
		quint8 result;
		stream >> result;
		json["result"] = result;

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}

	case 0x0007: // device state
	{
		// desc
		json["desc"] = "device state";
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));
		// work mode
		quint8 work_mode;
		stream >> work_mode;
		json["work_mode"] = work_mode;
		if (work_mode == 0x00)
		{
			json["work_mode_desc"] = "normal";
		}
		else if (work_mode == 0x01)
		{
			json["work_mode_desc"] = "sleep";
		}
		// ignition
		quint8 ignition;
		stream >> ignition;
		json["ignition"] = ignition;
		if (ignition == 0x00)
		{
			json["ignition_desc"] = "off";
		}
		else if (ignition == 0x01)
		{
			json["ignition_desc"] = "on";
		}
		// battery voltage
		quint16 battery_voltage;
		stream >> battery_voltage;
		json["battery_voltage"] = QString::number(battery_voltage) + "mV";
		// supply voltage
		quint16 supply_voltage;
		stream >> supply_voltage;
		json["supply_voltage"] = QString::number(supply_voltage) + "mV";
		// GSM signal strength
		quint8 gsm_signal_strength;
		stream >> gsm_signal_strength;
		json["gsm_signal_strength"] = QString::number(2 * gsm_signal_strength - 113) + "dBm";

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x0008: // trip mileage
	{
		// desc
		json["desc"] = "trip mileage";

		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time_of_acc_on"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));

		// longitude of acc on
		quint32 longitude;
		stream >> longitude;
		// high byte is east or west
		if (longitude & 0x80000000)
		{
			json["longitude"] = QString::number((double)(longitude & 0x7fffffff) / 1000000.0, 'f', 6) + "W";
		}
		else
		{
			json["longitude"] = QString::number((double)longitude / 1000000.0, 'f', 6) + "E";
		}
		// latitude of acc on
		quint32 latitude;
		stream >> latitude;
		// high byte is north or south
		if (latitude & 0x80000000)
		{
			json["latitude"] = QString::number((double)(latitude & 0x7fffffff) / 1000000.0, 'f', 6) + "S";
		}
		else
		{
			json["latitude"] = QString::number((double)latitude / 1000000.0, 'f', 6) + "N";
		}
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time_of_acc_off"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));
		// longitude of acc off
		stream >> longitude;
		// high byte is east or west
		if (longitude & 0x80000000)
		{
			json["longitude_of_acc_off"] = QString::number((double)(longitude & 0x7fffffff) / 1000000, 'f', 6) + "W";
		}
		else
		{
			json["longitude_of_acc_off"] = QString::number((double)longitude / 1000000.0, 'f', 6) + "E";
		}
		// latitude of acc off
		stream >> latitude;
		// high byte is north or south
		if (latitude & 0x80000000)
		{
			json["latitude_of_acc_off"] = QString::number((double)(latitude & 0x7fffffff) / 1000000, 'f', 6) + "S";
		}
		else
		{
			json["latitude_of_acc_off"] = QString::number((double)latitude / 1000000, 'f', 6) + "N";
		}
		// current trip mileage
		quint32 current_trip_mileage;
		stream >> current_trip_mileage;
		json["current_trip_mileage"] = QString::number(current_trip_mileage) + "m";
		// history trip mileage
		quint32 history_trip_mileage;
		stream >> history_trip_mileage;
		json["history_trip_mileage"] = QString::number(history_trip_mileage) + "m";
		// current trip time
		quint32 current_trip_time;
		stream >> current_trip_time;
		json["current_trip_time"] = QString::number(current_trip_time) + "s";
		// max speed
		quint16 max_speed;
		stream >> max_speed;
		json["max_speed"] = QString::number((double)max_speed / 100.0, 'f', 1) + "m/s";
		// convert to km/h
		json["max_speed_km"] = QString::number((double)max_speed * 3.6 / 100.0, 'f', 1) + "km/h";
		// min speed
		quint16 min_speed;
		stream >> min_speed;
		json["min_speed"] = QString::number((double)min_speed / 100.0, 'f', 1) + "m/s";
		// convert to km/h
		json["min_speed_km"] = QString::number((double)min_speed * 3.6 / 100.0, 'f', 1) + "km/h";

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x0009: // heartbeat
	{
		// desc
		json["desc"] = "heartbeat";
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}
	case 0x000a: // ota response
	{
		// desc
		json["desc"] = "ota response";

		// time
		quint8 year, month, day, hour, minute, second;
		stream >> year >> month >> day >> hour >> minute >> second;
		json["time"] = QString("%1-%2-%3 %4:%5:%6").arg(year + 2000).arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0')).arg(hour, 2, 10, QLatin1Char('0')).arg(minute, 2, 10, QLatin1Char('0')).arg(second, 2, 10, QLatin1Char('0'));

		// result
		quint8 result;
		stream >> result;
		json["result"] = result;

		// to json string
		return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
	}

	default:
		return QString(data.toHex());
	}
}

MyTcpServerWidget::MyTcpServerWidget(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);
	m_payload_model = new QStandardItemModel(this);
	m_payload_model->setColumnCount(6);

	// QStringList() << "Time" << "Source" << "From" << "Payload" << "Data" << "Result"
	QStringList header;
	header << "Time"
		   << "Source"
		   << "From"
		   << "Payload"
		   << "Data"
		   << "Result";
	m_payload_model->setHorizontalHeaderLabels(header);

	ui.filter_name_comboBox->addItems(header);
	ui.filter_name_comboBox->setCurrentIndex(4);

	m_payload_proxy_model = new QSortFilterProxyModel(this);
	m_payload_proxy_model->setSourceModel(m_payload_model);
	m_payload_proxy_model->setFilterKeyColumn(4);

	ui.payload_tableView->setModel(m_payload_proxy_model);

	m_server = new QTcpServer(this);
	hide_tips_timer = new QTimer(this);

	connect(m_server, &QTcpServer::newConnection, this, &MyTcpServerWidget::on_tcp_client_connected);
	connect(ui.start_btn, &QPushButton::clicked, this, &MyTcpServerWidget::on_start_listen_btn_clicked);
	connect(ui.clear_btn, &QPushButton::clicked, this, &MyTcpServerWidget::on_clear_btn_clicked);
	connect(ui.export_btn, &QPushButton::clicked, this, &MyTcpServerWidget::on_export_csv_btn_clicked);
	connect(ui.filter_edit, &QLineEdit::textChanged, this, &MyTcpServerWidget::on_filter_edit_text_changed);
	connect(ui.send_msg_plainTextEdit, &QPlainTextEdit::textChanged, this, &MyTcpServerWidget::set_send_msg_btn_enable);
	connect(ui.send_pushButton, &QPushButton::clicked, this, &MyTcpServerWidget::on_send_msg_btn_clicked);
	connect(ui.add_user_payload, &QPushButton::clicked, this, &MyTcpServerWidget::on_add_user_payload_btn_clicked);
	connect(hide_tips_timer, &QTimer::timeout, this, &MyTcpServerWidget::on_tips_timer_timeout);

	// on user payload tableWidget item changed
	connect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);

	ui.tips_label->hide();

	// load user payload
	load_user_payload();

	// resize 1/4 of the screen
	QRect screenRect = QApplication::desktop()->screenGeometry();
	this->resize(screenRect.width() / 2, screenRect.height() / 2);
}

MyTcpServerWidget::~MyTcpServerWidget()
{

	// close server
	m_server->close();
	m_server->deleteLater();

	// close all client
	QList<QTcpSocket *> list = m_client_data.keys();
	for (int i = 0; i < list.size(); i++)
	{
		list[i]->close();
		list[i]->deleteLater();

		delete m_client_data[list[i]].timer;
	}
}

void MyTcpServerWidget::on_start_listen_btn_clicked()
{
	if (ui.start_btn->text() == "Start")
	{
		// start server
		if (m_server->listen(QHostAddress::Any, ui.port_edit->text().toInt()))
		{
			ui.start_btn->setText("Stop");

			// disable port edit
			ui.port_edit->setEnabled(false);

			// insert to payload_tableView
			struct Payload_tableWidget_ItemData data;
			data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
			data.source = "server";
			data.from = m_server->serverAddress().toString();
			data.payload = "start listening";
			insert_to_payload_tableWidget(data);
		}
	}
	else
	{
		// enable port edit
		ui.port_edit->setEnabled(true);
		// insert to payload_tableView
		struct Payload_tableWidget_ItemData data;
		data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
		data.source = "server";
		data.from = m_server->serverAddress().toString();
		data.payload = "stop listening";
		insert_to_payload_tableWidget(data);

		m_server->close();
		ui.start_btn->setText("Start");
	}
}

void MyTcpServerWidget::on_clear_btn_clicked()
{
	// ui.payload_tableView->setRowCount(0);
	m_payload_model->removeRows(0, m_payload_model->rowCount());
}

void MyTcpServerWidget::on_export_csv_btn_clicked()
{
	QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "", tr("Excel Files (*.xlsx)"));
	if (fileName.isEmpty())
	{
		return;
	}

	// QFile file(fileName);
	// if (!file.open(QIODevice::WriteOnly))
	// {
	// 	qDebug() << "open file failed: " << fileName;
	// 	return;
	// }

	// QTextStream out(&file);
	// out.setCodec("UTF-8");

	// // // header
	// // QStringList header;
	// // header << "time"
	// // 	   << "source"
	// // 	   << "from"
	// // 	   << "payload"
	// // 	   << "data"
	// // 	   << "result";

	// // get header list from payload_tableView header
	// QStringList header;
	// for (int i = 0; i < m_payload_model->columnCount(); i++)
	// {
	// 	header << m_payload_model->horizontalHeaderItem(i)->text();
	// }

	// out << header.join(",") << "\n";

	// // data
	// for (int i = 0; i < m_payload_model->rowCount(); i++)
	// {
	// 	QStringList data;
	// 	for (int j = 0; j < m_payload_model->columnCount(); j++)
	// 	{
	// 		data << m_payload_model->item(i, j)->text();
	// 	}
	// 	out << data.join(",") << "\n";
	// }

	// file.close();

	// use xlnt to write excel
	xlnt::workbook wb;
	auto ws = wb.active_sheet();
	// header horizontalHeaderItem
	for (int i = 0; i < m_payload_model->columnCount(); i++)
	{
		ws.cell(xlnt::cell_reference(1 + i, 1)).value(m_payload_model->horizontalHeaderItem(i)->text().toStdString());
	}

	// data
	for (int i = 0; i < m_payload_model->rowCount(); i++)
	{
		for (int j = 0; j < m_payload_model->columnCount(); j++)
		{
			ws.cell(xlnt::cell_reference(j + 1, i + 2)).value(m_payload_model->item(i, j)->text().toStdString());
		}
	}

	wb.save(fileName.toStdString());
}

void MyTcpServerWidget::insert_to_payload_tableWidget(const Payload_tableWidget_ItemData &data)
{
	QList<QStandardItem *> items;
	items << new QStandardItem(data.time);
	items << new QStandardItem(data.source);
	items << new QStandardItem(data.from);
	items << new QStandardItem(data.payload);
	items << new QStandardItem(data.data);
	items << new QStandardItem(data.result);
	m_payload_model->appendRow(items);

	// scroll to bottom
	if (ui.auto_scroll_checkBox->isChecked())
	{
		ui.payload_tableView->scrollToBottom();
	}
}

void MyTcpServerWidget::insert_to_client_tableWidget(const Client_tableWidget_ItemData &data)
{
	int row = ui.client_tableWidget->rowCount();
	ui.client_tableWidget->insertRow(row);
	ui.client_tableWidget->setItem(row, 0, new QTableWidgetItem(data.time));
	ui.client_tableWidget->setItem(row, 1, new QTableWidgetItem(data.uuid));
	ui.client_tableWidget->setItem(row, 2, new QTableWidgetItem(data.imei));
}

void MyTcpServerWidget::on_tcp_client_connected()
{
	QTcpSocket *client = m_server->nextPendingConnection();
	connect(client, &QTcpSocket::disconnected, this, &MyTcpServerWidget::on_tcp_client_disconnected);
	connect(client, &QTcpSocket::readyRead, this, &MyTcpServerWidget::on_tcp_client_ready_read);

	// create uuid and remove {} and - in uuid
	QString uuid = QUuid::createUuid().toString();
	uuid.remove(QChar('{'));
	uuid.remove(QChar('}'));
	uuid.remove(QChar('-'));

	// insert to client_tableWidget
	struct Client_tableWidget_ItemData data;
	data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
	data.uuid = uuid;
	insert_to_client_tableWidget(data);

	// insert to payload_tableView
	struct Payload_tableWidget_ItemData payload_data;
	payload_data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
	payload_data.source = "client-" + uuid;
	// payload_data.from = client->peerAddress().toString();
	payload_data.payload = "connected";
	insert_to_payload_tableWidget(payload_data);

	m_client_data[client].uuid = uuid;

	// set property in client
	client->setProperty("uuid", uuid);

	m_client_data[client].timer = new QTimer(this);

	// connect(m_client_data[client].timer, &QTimer::timeout, [this, client]()
	// 		{ client->close(); });

	// start timer 3min period
	m_client_data[client].timer->start(3 * 60 * 1000);
}

void MyTcpServerWidget::on_tcp_client_disconnected()
{
	QTcpSocket *client = qobject_cast<QTcpSocket *>(sender());
	if (client)
	{

		QString uuid = client->property("uuid").toString();

		// remove from client_tableWidget
		for (int i = 0; i < ui.client_tableWidget->rowCount(); i++)
		{
			if (ui.client_tableWidget->item(i, 1)->text() == uuid)
			{
				ui.client_tableWidget->removeRow(i);
				break;
			}
		}

		QString imei = client->property("imei").toString();

		// insert to payload_tableView
		struct Payload_tableWidget_ItemData data;
		data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
		data.source = "client-" + uuid;
		data.from = imei;
		data.payload = "disconnected";
		insert_to_payload_tableWidget(data);

		delete m_client_data[client].timer;

		// remove from m_client_data
		m_client_data.remove(client);

		client->deleteLater();
	}
}

void MyTcpServerWidget::on_tcp_client_ready_read()
{
	QTcpSocket *client = qobject_cast<QTcpSocket *>(sender());
	if (client)
	{
		QString imei = client->property("imei").toString();
		if (imei.isEmpty())
		{
			// use ip as imei
			imei = client->peerAddress().toString();
		}

		// read data to hex string
		QByteArray data = client->readAll();
		m_client_data[client].data.append(data);

		// payload: aa ff total_length(2) imei(8,bcd code) version(2) data_length(2) data(0~1024) crc(2) 0x0d 0x0a
		while (m_client_data[client].data.size() >= 20)
		{
			QByteArray &data = m_client_data[client].data;
			// check if start with 0xaa 0xff
			if (data[0] != (char)0xaa || data[1] != (char)0xff)
			{
				// remove first byte
				data.remove(0, 1);
				continue;
			}

			// check if total length is enough
			quint16 total_length = (quint8)data[2] << 8 | (quint8)data[3];
			if (data.size() < total_length)
			{
				break;
			}

			// check if end with 0x0d 0x0a
			if (data[total_length - 2] != (char)0x0d || data[total_length - 1] != (char)0x0a)
			{
				// remove first byte
				data.remove(0, 1);
				continue;
			}

			// check crc
			quint16 crc = (quint8)data[total_length - 4] << 8 | (quint8)data[total_length - 3];
			quint16 crc_calc = mj_crc16(data.constData(), total_length - 4);
			if (crc != crc_calc)
			{
				qDebug() << "crc error: " << QString::number(crc, 16) << " " << QString::number(crc_calc, 16);
				// remove first byte
				data.remove(0, 1);
				continue;
			}

			// decode imei from bcd code
			imei = QString::fromLatin1(data.mid(4, 8).toHex());
			// remove first 0 if imei start with 0
			if (imei.startsWith("0"))
			{
				imei.remove(0, 1);
			}

			QString uuid = client->property("uuid").toString();

			// remove previous imei in client_tableWidget
			for (int i = 0; i < ui.client_tableWidget->rowCount(); i++)
			{
				// ignore self
				if (ui.client_tableWidget->item(i, 1)->text() == uuid)
				{
					continue;
				}

				if (ui.client_tableWidget->item(i, 2)->text() == imei)
				{
					auto previous_uuid = ui.client_tableWidget->item(i, 1)->text();
					// close previous client
					// find client at m_client_data
					QList<QTcpSocket *> list = m_client_data.keys();
					for (int i = 0; i < list.size(); i++)
					{
						if (list[i]->property("uuid").toString() == previous_uuid)
						{
							list[i]->close();
							list[i]->deleteLater();
							delete m_client_data[list[i]].timer;
							m_client_data.remove(list[i]);
							break;
						}
					}
					break;
				}
			}

			// set imei to client
			client->setProperty("imei", imei);

			// find uuid in client_tableWidget
			for (int i = 0; i < ui.client_tableWidget->rowCount(); i++)
			{
				if (ui.client_tableWidget->item(i, 1)->text() == uuid)
				{
					ui.client_tableWidget->item(i, 2)->setText(imei);
					break;
				}
			}

			quint16 data_length = (quint8)data[14] << 8 | (quint8)data[15];
			// data ref
			QByteArray payload = data.mid(16, total_length - 20);

			// decrypt
			yd_dec((uint8_t *)payload.data(), payload.size(), (uint8_t *)payload.data());

			quint16 cmd = (quint8)payload[0] << 8 | (quint8)payload[1];

			QString hex = data.left(total_length).toHex().toUpper();
			// insert to payload_tableView
			struct Payload_tableWidget_ItemData payload_data;
			payload_data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
			payload_data.source = "client-" + uuid;
			payload_data.from = imei;
			payload_data.payload = hex;
			payload_data.data = payload.left(data_length).toHex().toUpper();
			payload_data.result = decode_payload(payload.left(data_length));
			insert_to_payload_tableWidget(payload_data);

			qDebug() << "imei: " << imei
					 << " version: " << (quint8)data[10] << (quint8)data[11]
					 << " data length: " << data_length
					 << " cmd: " << QString::number(cmd, 16)
					 << " data: " << payload.toHex().toUpper();

			if (cmd == 0x0001) // login
			{
				// send login response
				QByteArray data;
				QDataStream stream(&data, QIODevice::WriteOnly);

				stream << (quint16)0xf001;

				// result
				stream << (quint8)0x00;
				// time
				QDateTime time = QDateTime::currentDateTime();
				// year
				qint16 year = time.date().year() - 2000;
				stream << (quint8)year;
				// month
				stream << (quint8)time.date().month();
				// day
				stream << (quint8)time.date().day();
				// hour
				stream << (quint8)time.time().hour();
				// minute
				stream << (quint8)time.time().minute();
				// second
				stream << (quint8)time.time().second();

				// encode
				QByteArray payload;
				encode_payload(imei, data, payload);

				// send
				client->write(payload);
				// flush
				client->flush();

				struct Payload_tableWidget_ItemData payload_tableWidget_item;
				payload_tableWidget_item.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
				payload_tableWidget_item.source = "server-" + uuid;
				payload_tableWidget_item.from = imei;
				payload_tableWidget_item.payload = payload.toHex().toUpper();
				payload_tableWidget_item.data = data.toHex().toUpper();
				payload_tableWidget_item.result = decode_payload(data);

				insert_to_payload_tableWidget(payload_tableWidget_item);
			}

			// remove first total_length bytes
			data.remove(0, total_length);

			// reset timer
			m_client_data[client].timer->start();
		}
	}
}

void MyTcpServerWidget::on_filter_edit_text_changed(const QString &text)
{
	// find text in payload column
	QString header_column_to_filter = ui.filter_name_comboBox->currentText();
	// find column index
	int column_index = 4;
	for (int i = 0; i < m_payload_model->columnCount(); i++)
	{
		if (m_payload_model->headerData(i, Qt::Horizontal).toString() == header_column_to_filter)
		{
			column_index = i;
			break;
		}
	}
	// set filter
	m_payload_proxy_model->setFilterKeyColumn(column_index);
	// use regular expression
	QRegExp regExp(text, Qt::CaseInsensitive, QRegExp::Wildcard);
	m_payload_proxy_model->setFilterRegExp(regExp);
}
void MyTcpServerWidget::set_send_msg_btn_enable()
{
	// hex regExp
	QRegExp regExp("[0-9a-fA-F]+");
	QString text = ui.send_msg_plainTextEdit->toPlainText();
	// remove space and \r\n
	text.remove(QRegExp("\\s"));
	ui.send_pushButton->setEnabled(!text.isEmpty() && (text.size() % 2 == 0) && regExp.exactMatch(text));
}

void MyTcpServerWidget::on_send_msg_btn_clicked()
{
	send_message(ui.send_msg_plainTextEdit->toPlainText());
}

void MyTcpServerWidget::send_message(const QString &msg)
{

	QRegExp regExp("[0-9a-fA-F]+");
	QString text = msg; // ui.send_msg_plainTextEdit->toPlainText();

	// remove space and \r\n
	text.remove(QRegExp("\\s"));

	if (text.isEmpty() || (text.size() % 2 != 0) || !regExp.exactMatch(text))
	{
		show_tips("invalid message");
		return;
	}

	QString imei;
	QTableWidgetItem *item = ui.client_tableWidget->currentItem();
	if (!item)
	{
		// insert to payload_tableView
		struct Payload_tableWidget_ItemData data;
		data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
		data.source = "server";
		data.from = "server";
		data.payload = "no client selected";
		insert_to_payload_tableWidget(data);

		show_tips("no client selected, please select one client");
		return;
	}

	// get imei
	imei = ui.client_tableWidget->item(item->row(), 2)->text();
	if (imei.isEmpty())
	{
		// insert to payload_tableView
		struct Payload_tableWidget_ItemData data;
		data.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
		data.source = "server";
		data.from = "server";
		data.payload = "no imei";
		insert_to_payload_tableWidget(data);

		show_tips("no imei at selected client");

		return;
	}

	auto uuid = ui.client_tableWidget->item(item->row(), 1)->text();

	QByteArray data = QByteArray::fromHex(text.toLatin1());

	// encode
	QByteArray payload;

	if (ui.raw_checkBox->isChecked())
	{
		payload = data;
	}
	else
	{
		encode_payload(imei, data, payload);
	}

	// find client by imei
	QList<QTcpSocket *> list = m_client_data.keys();
	for (int i = 0; i < list.size(); i++)
	{
		if (list[i]->property("uuid").toString() == uuid)
		{
			// send
			list[i]->write(payload);
			// flush
			list[i]->flush();

			// insert to payload_tableView
			struct Payload_tableWidget_ItemData payload_tableWidget_item;
			payload_tableWidget_item.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
			payload_tableWidget_item.source = "server";
			payload_tableWidget_item.from = imei;
			payload_tableWidget_item.payload = payload.toHex().toUpper();
			payload_tableWidget_item.data = data.toHex().toUpper();
			payload_tableWidget_item.result = ui.raw_checkBox->isChecked() ? "raw" : decode_payload(data);

			insert_to_payload_tableWidget(payload_tableWidget_item);

			break;
		}
	}
}

void MyTcpServerWidget::load_user_payload()
{
	disconnect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);
	// load user_payload from excel
	xlnt::workbook wb;
	try
	{
		wb.load("user_payload.xlsx");
		auto ws = wb.active_sheet();
		bool skip_header = false;
		for (auto row : ws.rows(false))
		{
			// skip header
			if (!skip_header)
			{
				skip_header = true;
				continue;
			}
			add_send_msg_item(UserPayload_tableWidget_ItemData(row[0].to_string().c_str(), row[1].to_string().c_str()));
		}
	}
	catch (const std::exception &e)
	{
		qDebug() << e.what();
	}

	connect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);
}

void MyTcpServerWidget::add_send_msg_item(const UserPayload_tableWidget_ItemData &data)
{
	// insert to user_payload_tableWidget
	QList<QTableWidgetItem *> items;
	items << new QTableWidgetItem(data.description);
	items << new QTableWidgetItem(data.data);

	int row = ui.user_payload_tableWidget->rowCount();
	ui.user_payload_tableWidget->insertRow(row);

	for (int i = 0; i < items.size(); i++)
	{
		ui.user_payload_tableWidget->setItem(row, i, items[i]);
	}

	int w = 80, h = 40;

	// add operation
	QPushButton *send_btn = new QPushButton("Send");
	send_btn->setObjectName("send_btn");
	// set button width and height
	send_btn->setFixedSize(w, h);
	connect(send_btn, &QPushButton::clicked, this, &MyTcpServerWidget::on_send_user_payload_btn_clicked);

	QPushButton *select_btn = new QPushButton("Select");
	select_btn->setObjectName("select_btn");
	// set button width and height
	select_btn->setFixedSize(w, h);

	connect(select_btn, &QPushButton::clicked, this, &MyTcpServerWidget::on_select_user_payload_btn_clicked);

	// delete btn;
	QPushButton *delete_btn = new QPushButton("Delete");
	delete_btn->setObjectName("delete_btn");
	// set button width and height
	delete_btn->setFixedSize(w, h);
	connect(delete_btn, &QPushButton::clicked, this, &MyTcpServerWidget::on_delete_user_payload_btn_clicked);

	// hor layout
	QHBoxLayout *horLayout = new QHBoxLayout;
	horLayout->addWidget(send_btn);
	horLayout->addWidget(select_btn);
	horLayout->addWidget(delete_btn);

	// set alignment
	horLayout->setAlignment(Qt::AlignCenter);

	// create widget
	QWidget *widget = new QWidget;
	widget->setLayout(horLayout);

	// set widget
	ui.user_payload_tableWidget->setCellWidget(row, 2, widget);

	// set cell height
	ui.user_payload_tableWidget->setRowHeight(row, h + 20);

	// set cell width
	ui.user_payload_tableWidget->setColumnWidth(2, w * 3 + 20);
}

void MyTcpServerWidget::on_add_user_payload_btn_clicked()
{
	disconnect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);
	// add user payload
	add_send_msg_item(UserPayload_tableWidget_ItemData("--", "--"));
	connect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);

	// scoll to bottom
	ui.user_payload_tableWidget->scrollToBottom();
}

void MyTcpServerWidget::on_user_payload_tableWidget_item_changed(QTableWidgetItem *item)
{
	// check if the column 0 contain , or not
	if (item->text().contains(","))
	{
		show_tips("description or data can not contain ','");
		// restore previous text
		item->setText(item->data(Qt::UserRole).toString());
		return;
	}

	if (item->column() == 0 || item->column() == 1)
	{
		// save user payload
		save_user_payload();
	}
}

void MyTcpServerWidget::on_delete_user_payload_btn_clicked()
{
	// get sender
	QPushButton *delete_btn = qobject_cast<QPushButton *>(sender());

	for (int i = 0; i < ui.user_payload_tableWidget->rowCount(); i++)
	{
		auto widget = ui.user_payload_tableWidget->cellWidget(i, 2);
		auto btn = widget->findChild<QPushButton *>("delete_btn");
		if (btn == delete_btn)
		{
			disconnect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);
			ui.user_payload_tableWidget->removeRow(i);
			connect(ui.user_payload_tableWidget, &QTableWidget::itemChanged, this, &MyTcpServerWidget::on_user_payload_tableWidget_item_changed);

			save_user_payload();
			break;
		}
	}
}

void MyTcpServerWidget::on_select_user_payload_btn_clicked()
{
	// get sender
	QPushButton *select_btn = qobject_cast<QPushButton *>(sender());

	for (int i = 0; i < ui.user_payload_tableWidget->rowCount(); i++)
	{
		auto widget = ui.user_payload_tableWidget->cellWidget(i, 2);
		auto btn = widget->findChild<QPushButton *>("select_btn");
		if (btn == select_btn)
		{
			auto item = ui.user_payload_tableWidget->item(i, 1);

			ui.send_msg_plainTextEdit->setPlainText(item->text());
			break;
		}
	}
}

void MyTcpServerWidget::on_send_user_payload_btn_clicked()
{
	// get sender
	QPushButton *send_btn = qobject_cast<QPushButton *>(sender());

	for (int i = 0; i < ui.user_payload_tableWidget->rowCount(); i++)
	{
		auto widget = ui.user_payload_tableWidget->cellWidget(i, 2);
		auto btn = widget->findChild<QPushButton *>("send_btn");
		if (btn == send_btn)
		{
			auto item = ui.user_payload_tableWidget->item(i, 1);

			send_message(item->text());
			break;
		}
	}
}

void MyTcpServerWidget::save_user_payload()
{
	// use xlnt to write excel
	xlnt::workbook wb;
	auto ws = wb.active_sheet();
	// header horizontalHeaderItem
	for (int i = 0; i < ui.user_payload_tableWidget->columnCount() - 1; i++)
	{
		ws.cell(xlnt::cell_reference(1 + i, 1)).value(ui.user_payload_tableWidget->horizontalHeaderItem(i)->text().toStdString());
	}

	// data
	for (int i = 0; i < ui.user_payload_tableWidget->rowCount(); i++)
	{
		auto description = ui.user_payload_tableWidget->item(i, 0)->text();
		auto data = ui.user_payload_tableWidget->item(i, 1)->text();

		if (description.isEmpty() || data.isEmpty())
		{
			continue;
		}

		if (description == "--" && data == "--")
		{
			continue;
		}

		ws.cell(xlnt::cell_reference(1, i + 2)).value(description.toStdString());
		ws.cell(xlnt::cell_reference(2, i + 2)).value(data.toStdString());
	}

	wb.save("user_payload.xlsx");
}

void MyTcpServerWidget::on_tips_timer_timeout()
{
	ui.tips_label->clear();
	// stop hide timer
	hide_tips_timer->stop();
	// hide tips label
	ui.tips_label->hide();
}

void MyTcpServerWidget::show_tips(const QString &tips)
{
	ui.tips_label->setText(tips);
	ui.tips_label->show();
	// stop hide timer
	if (hide_tips_timer->isActive())
	{
		hide_tips_timer->stop();
	}
	// start hide timer
	hide_tips_timer->start(3000);
}